#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Configuration;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;

namespace Osh.Configuration
{
	public delegate void ConfigurationEventHandler(object sender, EventArgs args);

	/// <summary>
	/// Summary description for Configuration.
	/// </summary>
	public sealed class Configuration
	{
		public static string ApplicationName = "On-Screen HTML";
		private static string ConfigFileName = "OSH.config";
		private static string DataFolderName = "Data";
		private static Configuration _current;

		private System.Configuration.Configuration _config;
		private string _productName;
		private string _startupPath = null;
		private string _logFolder;
		private static TraceSource _oshTraceSource = new TraceSource("Osh");
		private TraceSource _mediaTraceSource = new TraceSource("Osh.Media");
		private TraceSource _teletextTraceSource = new TraceSource("Osh.Teletext");

		public Configuration()
		{
			ExeConfigurationFileMap map = new ExeConfigurationFileMap();
			map.ExeConfigFilename = GetOshConfigPath();
			// Common (all users) configuration is used instead of roaming.
			map.RoamingUserConfigFilename = Path.Combine(this.CommonApplicationFolder, ConfigFileName);
			map.LocalUserConfigFilename = Path.Combine(this.LocalApplicationFolder, ConfigFileName);

			FixConfiguration(map);
			_config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.PerUserRoamingAndLocal);
		}
		
		public System.Configuration.ConfigurationSection GetSection(string sectionName)
		{
			return _config.GetSection(sectionName);
		}

		public void Save()
		{
			_config.Save(ConfigurationSaveMode.Minimal, false);
			if(this.Changed != null)
				this.Changed(this, null);
		}

		public string ProductName
		{
			get
			{
				if(_productName == null)
				{
					AssemblyProductAttribute attribute = (AssemblyProductAttribute)Attribute.GetCustomAttribute(
						Assembly.GetExecutingAssembly(), typeof(AssemblyProductAttribute));
					if(attribute == null || string.IsNullOrEmpty(attribute.Product))
						throw new InvalidOperationException("Unknown product name.");
					_productName = attribute.Product;
				}
				return _productName;
			}
		}

		public string FilePath
		{
			get { return _config.FilePath; }
		}

		private AppSettingsSection AppSettings
		{
			get
			{
				return (AppSettingsSection)this.GetSection("appSettings");
			}
		}

		public ConnectionStringSettingsCollection ConnectionStrings
		{
			get
			{
				return ((ConnectionStringsSection)this.GetSection("connectionStrings")).ConnectionStrings;
			}
		}

		public ConnectionStringSettings GetConnectionStringSettings(string name)
		{
			return this.ConnectionStrings[name];
		}

		/// <summary>
		/// The root directory where OSH is installed (like "C:\Program Files\On-Screen HTML").
		/// </summary>
		public string GlobalApplicationFolder
		{
			get { return Path.GetDirectoryName(this.StartupPath); }
		}

		/// <summary>
		/// The root directory where OSH is installed (like "C:\Program Files\On-Screen HTML\Data").
		/// </summary>
		public string GlobalApplicationDataFolder
		{
			get { return Path.Combine(Path.GetDirectoryName(this.StartupPath), DataFolderName); }
		}

		/// <summary>
		/// The directory that serves as a common repository for application-specific data that is used by all users.
		/// </summary>
		public string CommonApplicationFolder
		{
			get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), this.ProductName); }
		}

		/// <summary>
		/// User's local application folder (e.g. "C:\Users\[UserName]\AppData\Local\On-Screen HTML").
		/// </summary>
		public string LocalApplicationFolder
		{
			get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), this.ProductName); }
		}

		/// <summary>
		/// User's local application data folder (e.g. "C:\Users\[UserName]\AppData\Local\On-Screen HTML\Data").
		/// </summary>
		public string LocalApplicationDataFolder
		{
			get { return Path.Combine(this.LocalApplicationFolder, DataFolderName); }
		}

		public string LogFolder
		{
			get
			{
				if(_logFolder == null)
				{
                    string logFolder = GetAppSetting<string>("LogFolder", null);
					if(logFolder == null)
						logFolder = @"Logs";
					if(!Path.IsPathRooted(logFolder))
						logFolder = Path.Combine(this.LocalApplicationFolder, logFolder);
					_logFolder = logFolder;
				}
				return _logFolder;
			}
		}

		public OshSection Osh
		{
			get { return (OshSection)_config.GetSection("osh"); }
		}

		public ProgDvb.ProgDvbSection ProgDvb
		{
			get { return (ProgDvb.ProgDvbSection)_config.GetSection("progDvb"); }
		}

		public event ConfigurationEventHandler Changed;


		private string GetOshConfigPath()
		{
			return Path.Combine(this.StartupPath, @"..\Html\OSH.config");
		}
		
		public string MapPath(string path)
		{
			if(string.IsNullOrEmpty(path))
				throw new ArgumentNullException("path");

			if(path.StartsWith("~/"))
				return Path.Combine(this.HtmlRootFolder, path.Substring(2));

			if(Path.IsPathRooted(path))
				return path;

			return Path.Combine(this.HtmlRootFolder, path);
		}

		private string StartupPath
		{
			get
			{
				if(_startupPath == null)
				{
					StringBuilder sb = new StringBuilder(260);
					Win32.GetModuleFileName(new HandleRef(null, IntPtr.Zero), sb, sb.Capacity);
					_startupPath = Path.GetDirectoryName(sb.ToString());
				}
				return _startupPath;
			}
		}

		private string HtmlRootFolder
		{
			get
			{
				string htmlRootFolder = this.GetAppSetting<string>("HtmlRootFolder", @"..\Html");
				if(!Path.IsPathRooted(htmlRootFolder))
				    htmlRootFolder = Path.Combine(this.StartupPath, htmlRootFolder);
				return htmlRootFolder;
			}
		}

		internal static TraceSource TraceSource
		{
			get { return _oshTraceSource; }
		}

		public TraceSource OshTraceSource
		{
			get { return _oshTraceSource; }
		}
		
		public TraceSource MediaTraceSource
		{
			get { return _mediaTraceSource; }
		}

		public TraceSource TeletextTraceSource
		{
			get { return _teletextTraceSource; }
		}

		public static Configuration Current
		{
			get
			{
				if(_current == null)
					_current = new Configuration();
				return _current;
			}
		}

		private T GetAppSetting<T>(string name, T defaultValue)
		{
			KeyValueConfigurationElement setting = this.AppSettings.Settings[name];
			if(setting == null)
				return defaultValue;

			string value = setting.Value;
			if(string.IsNullOrEmpty(value))
				return defaultValue;

			return (T)Convert.ChangeType(value, typeof(T));
		}

		/// <summary>
		/// Checks user configuration file and removes all invalid sections which could be left from previous versions.
		/// </summary>
		/// <param name="map"></param>
		private void FixConfiguration(ExeConfigurationFileMap map)
		{
			System.Configuration.Configuration config =
				ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.PerUserRoamingAndLocal);

			if(!File.Exists(map.LocalUserConfigFilename))
				return;

			XmlDocument xml = new XmlDocument();
			xml.Load(map.LocalUserConfigFilename);
			bool docChanged = false;
			foreach(XmlElement sectionElement in xml.SelectNodes("/configuration/*"))
			{
				if(sectionElement.LocalName == "configSections")
					continue;

				try
				{
					if(config.Sections[sectionElement.LocalName] != null)
						continue;
				}
				catch(System.Configuration.ConfigurationErrorsException e)
				{
					Log.Exception(TraceSource, e,
						"Cannot read config section '{0}'. The section will be removed.", sectionElement.LocalName);
				}
				sectionElement.ParentNode.RemoveChild(sectionElement);
				docChanged = true;
			}
			if(docChanged)
				xml.Save(map.LocalUserConfigFilename);
		}
	}
}
